TS手写简陋版reactive响应式原理(依赖收集,依赖更新)

您所在的位置:网站首页 spring响应式编程 pdf TS手写简陋版reactive响应式原理(依赖收集,依赖更新)

TS手写简陋版reactive响应式原理(依赖收集,依赖更新)

2023-06-02 09:27| 来源: 网络整理| 查看: 265

最近博主看源码了解到了vue3的响应式原理

vue3的响应式实现是依赖收集与依赖更新,vue3已经从Object.property更换成Proxy,Proxy相比于前者可以直接监听对象数组,对于深层次的对象和数组,会把触发对应getter,然后去递归进行依赖收集,并不是直接像vue2暴力那样递归,总体而言性能更好

实现原理:

通过Proxy(代理):拦截对象中任意属性的变化,包括属性的读写,属性的添加,属性的删除等。通过Reflect(反射):对源对象的属性进行操作

接下来博主带你们手写一波简单的源码操作

对reactive传进来的对象进行Proxy进行劫持在内部进行依赖收集与通知更新操作 import { track,trigger } from "./effect" //判断是否是对象的 const isObject = (target) => target!=null && typeof target == 'object' export const reactive = (target:T) =>{ return new Proxy(target,{ //返回三个参数 /** * * @param target 传入的当前对象 * @param key 传入的当前属性 * @param receiver 其实也是当前对象 */ get(target,key,receiver){ // return target[key] //某些情况特定情况会上下文错乱 //解决上下文错乱 //Reflect ES新增 对象取值,接收三个参数 let res = Reflect.get(target,key,receiver) as object //断言成object //依赖收集 track(target,key) //深层次的递归 if(isObject(res)){ return reactive(res) } return res }, //需要返回一个布尔值 set(target,key,value,receiver){ //Reflect.set正好返回一个布尔值 let res = Reflect.set(target,key,value,receiver) //依赖更新 trigger(target,key) return res } }) }

 

采用Reflet对对象进行标准化操作,因为如果直接采用JS如果失败了,不会产生异常提示

这样在进行获取数据是后进行依赖收集,在更新数据后进行通知依赖更新

补充知识点

1.Reflect.get()

Reflect.get(target, propertyKey[, receiver]) 用于从对象中获取属性的值

target: 目标对象 propertyKey: 需要获取值的属性名称 receiver: 如果遇到getter,此值将提供给目标调用

2.Reflect.set()

Reflect.set(target, propertyKey, value[, receiver])

在对象上设置属性,返回一个Bool值来表示是否成功设置属性。

target: 目标对象 propertyKey: 设置的属性名称 value: 设置的属性值 receiver: 如果遇到setter, this将提供给目标调用

依赖收集

//依赖收集 /** * * @param target 接收这个对象 * @param key */ //需要一个全局变量把它收集起来 //WeakMap只接收object的类型 //target正好是一个对象 const targetMap = new WeakMap() export const track = (target,key) =>{ let firstDeepMap = targetMap.get(target) //第一层数据结构 //第一次是没有这个值的 if(!firstDeepMap){ //所以给它填充一下 firstDeepMap = new Map() //通过targetMap 给它添加进去 targetMap.set(target,firstDeepMap) } //第二层数据结构 let secondDeepMap = firstDeepMap.get(key) //通过这个key 去取第二层的new Set //第一次没有值 取不到 需要填充 if(!secondDeepMap){ secondDeepMap = new Set() firstDeepMap.set(key,secondDeepMap) } //第三层把它关联起来(effect,effect...) secondDeepMap.add(activeEffect) }

我们首先new了一个weakMap,weakMap只接受object的类型,target正好是一个对象,然后我们通过target获取到对应的内部Map,我们在通过key获取到Set的集合,此时内部存储的就是一个个所收集到的依赖

这里使用WeakMap原因是它是一个弱引用,不会影响垃圾回收机制回收。

activeEffect是什么呢

effect可以接收一个匿名的函数 客户可以自己去自定义

匿名函数我们每次把它收集起来 然后依赖发生变化时  去执行这个里面副作用函数,实现依赖收集和依赖更新

我们自己定义了一个简陋的版本,闭包收集去执行它

//effect可以接收一个匿名的函数 客户可以自己去自定义 //匿名函数我们每次把它收集起来 然后依赖发生变化时 去执行这个里面副作用函数,实现依赖收集和依赖更新 //定义一个全局变量 把这个闭包给收集起来 //简陋版本 let activeEffect; export const effect = (fn:Function) =>{ //闭包 const _effect = function(){ activeEffect = _effect //收集起来执行一下 fn() } //首次默认给它调用一下 _effect() }

 我们简单的可以就把他理解成一个依赖,用户使用了effect函数过后,里面的响应式数据发生变化后会重新执行传递进去的回调函数,vue2中收集的依赖对应watcher,vue3收集的依赖实际是effect,他们两者实现功能实际上是一样的。

依赖更新

这里暂不考虑DOM问题,操作起来其实很简单就是通过被Proxy劫持的target与key找到对应的Set集合调用用户传递的effect函数进行依赖更新

//依赖更新 //通过全局变量targetMap取到firstDeepMap export const trigger = (target,key) =>{ const firstDeepMap = targetMap.get(target) const secondDeepMap = firstDeepMap.get(key) //收集到的就是一个副作用函数effect,进行更新 secondDeepMap.forEach(effect => effect()) }

则全部代码整理如下effect.ts

//effect可以接收一个匿名的函数 客户可以自己去自定义 //匿名函数我们每次把它收集起来 然后依赖发生变化时 去执行这个里面副作用函数,实现依赖收集和依赖更新 //定义一个全局变量 把这个闭包给收集起来 //简陋版本 let activeEffect; export const effect = (fn:Function) =>{ //闭包 const _effect = function(){ activeEffect = _effect //收集起来执行一下 fn() } //首次默认给它调用一下 _effect() } //依赖收集 /** * * @param target 接收这个对象 * @param key */ //需要一个全局变量把它收集起来 //WeakMap只接收object的类型 //target正好是一个对象 const targetMap = new WeakMap() export const track = (target,key) =>{ let firstDeepMap = targetMap.get(target) //第一层数据结构 //第一次是没有这个值的 if(!firstDeepMap){ //所以给它填充一下 firstDeepMap = new Map() //通过targetMap 给它添加进去 targetMap.set(target,firstDeepMap) } //第二层数据结构 let secondDeepMap = firstDeepMap.get(key) //通过这个key 去取第二层的new Set //第一次没有值 取不到 需要填充 if(!secondDeepMap){ secondDeepMap = new Set() firstDeepMap.set(key,secondDeepMap) } //第三层把它关联起来(effect,effect...) secondDeepMap.add(activeEffect) } //依赖更新 //通过全局变量targetMap取到firstDeepMap export const trigger = (target,key) =>{ const firstDeepMap = targetMap.get(target) const secondDeepMap = firstDeepMap.get(key) //收集到的就是一个副作用函数effect,进行更新 secondDeepMap.forEach(effect => effect()) }

最后index.html可以执行

Document import { reactive } from "./reactive.js"; import { effect } from "./effect.js"; const user = reactive({ name: "xiaochen", age: 18, foo: { bar: { sss: 123, }, }, }); effect(() => { document.querySelector( "#app" ).innerText = `${user.name} - ${user.age}-${user.foo.bar.sss}`; }); setTimeout(() => { user.name = "dachen"; setTimeout(() => { user.age = "23"; setTimeout(() => { user.foo.bar.sss = 66666666; }, 1000); }, 1000); }, 2000);



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3